#pragma once

#include <sstream>

#include "StringRef.h"

template<typename T> class Parser
{
public:
	class Node;
private:

	enum TokenType
	{
		NONE,
		NAME,
		NUMBER,
		END,
		PLUS='+',
		MINUS='-',
		MULTIPLY='*',
		DIVIDE='/',
		ASSIGN='=',
		LHPAREN='(',
		RHPAREN=')',
		COMMA=',',
		NOT='!',

		// comparisons
		LT='<',
		GT='>',
		LE,     // <=
		GE,     // >=
		EQ,     // ==
		NE,     // !=
		AND,    // &&
		OR,      // ||

		// special assignments

		ASSIGN_ADD,  //  +=
		ASSIGN_SUB,  //  +-
		ASSIGN_MUL,  //  +*
		ASSIGN_DIV   //  +/

	};


	const char *word;
	const char *wordStart;
	StringRef lastWord;
	T lastValue;

	// last token parsed
	TokenType type;


	TokenType getToken(const bool ignoreSign = false);  
	Node* commaList(const bool get);
	Node* expression(const bool get);
	Node* comparison(const bool get);
	Node* addSubtract(const bool get);
	Node* term(const bool get);      // multiply and divide
	Node* primary(const bool get);   // primary (base) tokens

	inline void checkToken(const TokenType wanted)
	{
		if(type != wanted)
		{
			std::ostringstream s;
			s << "'" << static_cast<char>(wanted) << "' expected.";
			throw StringRef(s.str().c_str(), true);    
		}
	}

	inline Node* simplify(Node *node)
	{
		if(node->isStandalone())
		{
			T value = node->getValue(0);
			delete node;
			return new ValueNode(value);
		}

		return node;
	}

public:
	class Register
	{
	public:
		virtual T getVariable(StringRef variable)=0;
		virtual T applyFunction(StringRef functionName, T parameter)=0;
	};

	class Node
	{
	public:
		inline Node() { }
		virtual ~Node() { }

		virtual T getValue(Register *r)=0;
		virtual bool isStandalone()=0;
	};

	class ValueNode: public Node
	{
		T value;
	public:
		inline ValueNode(T val): Node(), value(val) { }

		virtual ~ValueNode() { }

		virtual T getValue(Register *r)
		{
			return value;
		}

		virtual bool isStandalone()
		{
			return true;
		}
	};

	class VariableNode: public Node
	{
		StringRef name;
	public:
		inline VariableNode(StringRef varName): Node(), name(varName) { }

		virtual ~VariableNode() { }

		virtual T getValue(Register *r)
		{
			return r->getVariable(name);
		}

		virtual bool isStandalone()
		{
			return false;
		}
	};

#define UNARYNODE(className, op) \
	class className : public Node \
	{ \
		Node *n; \
	public: \
		inline className(Node *node): Node(), n(node) { } \
		\
                virtual ~ className() { if(n) delete n; } \
		\
		virtual T getValue(Register *r) \
		{ \
			return op n->getValue(r); \
		} \
		\
		virtual bool isStandalone() \
		{ \
			return n->isStandalone(); \
		} \
	}

#define BINARYNODE(className, op) \
	class className : public Node \
	{ \
		Node *left, *right; \
	public: \
		inline className(Node *leftNode, Node *rightNode): \
			Node(), left(leftNode), right(rightNode) { } \
		\
                virtual ~ className() { if(left) delete left; if(right) delete right; } \
		\
		virtual T getValue(Register *r) \
		{ \
			return left->getValue(r) op right->getValue(r); \
		} \
		\
		virtual bool isStandalone() \
		{ \
			return left->isStandalone() && right->isStandalone(); \
		} \
	}

	UNARYNODE(NegateNode, -);
	UNARYNODE(BoolNotNode, !);
	
	BINARYNODE(AddNode, +);
	BINARYNODE(SubtractNode, -);
	BINARYNODE(MultiplyNode, *);
	BINARYNODE(DivideNode, /);

	BINARYNODE(AndNode, &&);
	BINARYNODE(OrNode, ||);

#define COMMA ,
	BINARYNODE(CommaNode, COMMA);
#undef COMMA

	class FunctionNode: public Node
	{
		StringRef name;
		Node *param;
	public:
		inline FunctionNode(StringRef funName, Node *parameter):
			Node(), name(funName),
			param(parameter) { }

		virtual ~FunctionNode()
		{
			delete param;
		}

		virtual T getValue(Register *r)
		{
			return r->applyFunction(name, param->getValue(r));
		}

		virtual bool isStandalone()
		{
			return false;
		}
	};

	inline Node* parse(const char *expr)
	{
		wordStart = word = expr;
		lastWord = "";
		return commaList(true);
	}

	inline Node* operator()(const char *expr)
	{
		return parse(expr);
	}
};

template<typename T> typename Parser<T>::TokenType Parser<T>::getToken(const bool ignoreSign)
{
	// skip spaces
	while (*word && isspace(*word))
		++word;

	wordStart = word;   // remember where word starts *now*

	// look out for unterminated statements and things
	if(!*word &&  // we have EOF
		type == END)  // after already detecting it
		throw StringRef("Unexpected end of expression.");

	unsigned char firstCharacter = *word;        // first character in new word

	if(firstCharacter == 0)    // stop at end of file
	{
		word = "<end of expression>";
		return type = END;
	}

	unsigned char nextCharacter  = word[1];  // 2nd character in new word_

	// look for number
	// can be: + or - followed by a decimal point
	// or: + or - followed by a digit
	// or: starting with a digit
	// or: decimal point followed by a digit
	if((!ignoreSign &&
		(firstCharacter == '+' || firstCharacter == '-') &&
		(isdigit (nextCharacter) || nextCharacter == '.')
		)
		|| isdigit (firstCharacter)
	// allow decimal numbers without a leading 0. e.g. ".5"
	// Dennis Jones 01-30-2009
		|| (firstCharacter == '.' && isdigit (nextCharacter)) )
	{
		// skip sign for now
		if((firstCharacter == '+' || firstCharacter == '-'))
			word++;
		while(isdigit(*word) || *word == '.')
			word++;

		// allow for 1.53158e+15
		if(*word == 'e' || *word == 'E')
		{
			word++; // skip 'e'
			if((*word  == '+' || *word  == '-'))
				word++; // skip sign after e
			while(isdigit(*word))  // now digits after e
				word++;
		}

		lastWord = StringRef(wordStart, word-wordStart);
		lastValue = lastWord.toFloat(0);

		return type = NUMBER;
	}   // end of number found

	// special test for 2-character sequences: <= >= == !=
	// also +=, -=, /=, *=
	if(nextCharacter == '=')
	{
		switch (firstCharacter)
		{
		// comparisons
		case '=': type = EQ;   break;
		case '<': type = LE;   break;
		case '>': type = GE;   break;
		case '!': type = NE;   break;
		// assignments
		case '+': type = ASSIGN_ADD;   break;
		case '-': type = ASSIGN_SUB;   break;
		case '*': type = ASSIGN_MUL;   break;
		case '/': type = ASSIGN_DIV;   break;
		// none of the above
		default:  type = NONE; break;
		} // end of switch on firstCharacter

		if(type != NONE)
		{
			lastWord = StringRef(wordStart, 2);
			word += 2;   // skip both characters
			return type;
		} // end of found one    
	} // end of *=

	switch (firstCharacter)
	{
	case '&':
		if(nextCharacter == '&')    // &&
		{
			lastWord = StringRef(wordStart, 2);
			word += 2;   // skip both characters
			return type = AND;
		}
		break;
	case '|':
		if(nextCharacter == '|')   // ||
		{
			lastWord = StringRef(wordStart, 2);
			word += 2;   // skip both characters
			return type = OR;
		}
		break;
	// single-character symboles
	case '=':
	case '<':
	case '>':
	case '+':
	case '-':
	case '/':
	case '*':
	case '(':
	case ')':
	case ',':
	case '!':
		lastWord = StringRef(wordStart, 1);
		++word;   // skip it
		return type = TokenType(firstCharacter);
	} // end of switch on firstCharacter

	if(!isalpha(firstCharacter))
	{
		if(firstCharacter < ' ')
		{
			std::ostringstream s;
			s << "Unexpected character (decimal " << int(firstCharacter) << ")";
			throw StringRef(s.str().c_str(), true);    
		} else
		{
			char str[] = "Unexpected character:  ";
			str[sizeof(str)/sizeof(*str)-1] = firstCharacter;
			throw StringRef(str, true);
		}
	}

	// we have a word (starting with A-Z) - pull it out
	while(isalnum(*word) || *word == '_')
		++word;

	lastWord = StringRef(wordStart, word-wordStart);
	return type = NAME;
}

template<typename T> typename Parser<T>::Node* Parser<T>::commaList(const bool get)
{
	Node* left = expression(get);
	while(true)
	{
		switch (type)
		{
		case COMMA:
			left = simplify(new CommaNode(left, expression(true)));
			break; // discard previous value
		default:
			return left;
		} // end of switch on type
	}   // end of loop
}

template<typename T> typename Parser<T>::Node* Parser<T>::expression(const bool get)
{
	Node* left = comparison(get);
	while(true)
	{
		switch(type)
		{
			case AND: 
				{
					Node *right = comparison(true);
					left = simplify(new AndNode(left, right));
				}
				break;
			case OR:  
				{
					Node *right = comparison(true);
					left = simplify(new OrNode(left, right));
				}
				break;
			default:
				return left;
		} // end of switch on type
	}   // end of loop
}

template<typename T> typename Parser<T>::Node* Parser<T>::comparison(const bool get)
{
	return addSubtract(get);
}

template<typename T> typename Parser<T>::Node* Parser<T>::addSubtract(const bool get)
{
	Node* left = term(get);
	while(true)
	{
		switch (type)
		{
			case PLUS:
				left = simplify(new AddNode(left, term(true)));
				break;
			case MINUS:
				left = simplify(new SubtractNode(left, term(true)));
				break;
			default:
				return left;
		} // end of switch on type
	}   // end of loop
}

template<typename T> typename Parser<T>::Node* Parser<T>::term(const bool get)
{
	Node* left = primary(get);
	while(true)
	{
		switch (type)
		{
			case MULTIPLY:
				left = simplify(new MultiplyNode(left, term(true)));
				break;
			case DIVIDE:
				left = simplify(new DivideNode(left, term(true)));
				break;
			default:
				return left;
		} // end of switch on type
	}   // end of loop
}

template<typename T> typename Parser<T>::Node* Parser<T>::primary(const bool get)
{

	if(get)
		getToken();    // one-token lookahead  

	switch(type)
	{
	case NUMBER:  
	{
		T v = lastValue;
		getToken(true);  // get next one (one-token lookahead)
		return new ValueNode(v);
	}

	case NAME:
	{
		StringRef name = lastWord;
		getToken(true); 
		if(type == LHPAREN)
		{
			Node *param = expression(true);
			checkToken(RHPAREN);
			getToken(true);
			return simplify(new FunctionNode(name, param));
		}


		// not a function? must be a symbol in the symbol table
		return simplify(new VariableNode(name));
		// assignment operators could be handled here
	}

	case MINUS:               // unary minus
		return simplify(new NegateNode(primary(true)));

	case NOT:   // unary not
		return simplify(new BoolNotNode(primary(true)));

	case LHPAREN:
	{
		Node *n = commaList(true);    // inside parens, you could have commas
		checkToken(RHPAREN);
		getToken(true);                // eat the )
		return n;
	}

	default:
	{
		std::ostringstream s;
		s << "Unexpected token " << *lastWord;
		throw StringRef(s.str().c_str(), true);
	}

	} // end of switch on type
}

typedef Parser<float> FloatParser;

#ifdef _TEST
class MockFloatNode: public FloatParser::Node
{
public:
	inline MockFloatNode() { }
	virtual ~MockFloatNode() { }

	MOCK_METHOD1(getValue, float(typename FloatParser::Register *r));
	MOCK_METHOD0(isStandalone, bool());
};
#endif
